﻿using System.Windows;
using System.Windows.Input;
using Fluent;
using XmlStudio.Controllers;
using XmlStudio.Controllers.Interfaces;
using XmlStudio.Interfaces;
using XmlStudio.Models;
using XmlStudio.ViewModel;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;
using System;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.IO;
using System.IO.Packaging;
using System.Windows.Xps.Packaging;
using System.Windows.Xps;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using WPFExtensions.ViewModel.Commanding;

namespace XmlStudio {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : RibbonWindow, IXmlView {
        #region Fields
        #region Public
        #endregion

        #region Private
        IXmlModel xmlModel = ModelFactory.CreateXmlModel();
        IXsdModel xsdModel = ModelFactory.CreateXsdModel();
        IController controller;
        XmlViewModel viewModel;
        App application;

        private static readonly string defaultExceptionPolicy = "UIPolicy";
        #endregion
        #endregion

        #region Properties
        #region Public
        #endregion

        #region Private
        #endregion
        #endregion

        #region Constructors
        #region Public
        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow(App application, ApplicationMode mode) {
            InitializeComponent();

            Logger.Writer.Write("Starting XmlStudio.", "MainWindow", 10, 1, System.Diagnostics.TraceEventType.Information);

            this.application = application;
            this.application.AddWindow(this);

            switch(mode) {
                case ApplicationMode.Xml:
                    this.controller = ControllerFactory.CreateController(this.xmlModel, this);
                    this.viewModel = new XmlViewModel(this.application, this.controller, this.xmlModel, this);
                    DataContext = this.viewModel;
                    break;
                case ApplicationMode.Xsd:
                    this.controller = ControllerFactory.CreateController(this.xsdModel, this);
                    this.viewModel = new XsdViewModel(this.application, this.controller, this.xsdModel, this);
                    DataContext = this.viewModel;
                    break;
            }

            SetCommandBindings();
        }

        /// <summary>
        /// Invoked whenever an unhandled <see cref="E:System.Windows.FrameworkElement.ContextMenuOpening"/> routed event reaches this class in its route. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs"/> that contains the event data.</param>
        protected override void OnContextMenuOpening(ContextMenuEventArgs e) {
            base.OnContextMenuOpening(e);
        }

        /// <summary>
        /// Invoked whenever an unhandled <see cref="E:System.Windows.FrameworkElement.ContextMenuClosing"/> routed event reaches this class in its route. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">Provides data about the event.</param>
        protected override void OnContextMenuClosing(ContextMenuEventArgs e) {
            base.OnContextMenuClosing(e);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        /// <param name="application">The application.</param>
        /// <param name="path">The path.</param>
        /// <param name="mode">The mode.</param>
        public MainWindow(App application, string path, ApplicationMode mode) {
            InitializeComponent();

            Logger.Writer.Write("Starting XmlStudio.", "MainWindow", 10, 1, System.Diagnostics.TraceEventType.Information);

            this.application = application;
            this.application.AddWindow(this);

            switch(mode) {
                case ApplicationMode.Xml:
                    InitXmlMode(path);
                    break;
                case ApplicationMode.Xsd:
                    InitXsdMode(path);
                    break;
            }
        }
        #endregion

        #region Private
        #endregion
        #endregion

        #region Methods
        #region Public
        /// <summary>
        /// Inits the XML mode.
        /// </summary>
        /// <param name="path">The path.</param>
        public void InitXmlMode(string path) {
            this.xmlModel = ModelFactory.CreateXmlModel(path);
            this.controller = ControllerFactory.CreateController(this.xmlModel, this);

            if(this.viewModel != null) {
                this.viewModel.PropertyChanged -= viewModel_PropertyChanged;
                this.viewModel.Cleanup();
            }
            
            this.viewModel = new XmlViewModel(this.application, this.controller, this.xmlModel, this);
            this.viewModel.PropertyChanged += viewModel_PropertyChanged;
            DataContext = this.viewModel;

            SetCommandBindings();
        }

        /// <summary>
        /// Inits the XSD mode.
        /// </summary>
        /// <param name="path">The path.</param>
        public void InitXsdMode(string path) {
            this.xsdModel = ModelFactory.CreateXsdModel(path);
            this.xmlModel = xsdModel; // so we can use xmlModel in controller for xml part
            if(this.controller == null) {
                this.controller = ControllerFactory.CreateController(this.xsdModel, this);
            } else {
                this.controller.SetModel(this.xsdModel);
            }

            if(this.viewModel != null) {
                this.viewModel.PropertyChanged -= viewModel_PropertyChanged;
                this.CommandBindings.Clear();
                this.viewModel.Cleanup();
            }

            this.viewModel = new XsdViewModel(this.application, this.controller, this.xsdModel, this);
            this.viewModel.PropertyChanged += viewModel_PropertyChanged;
            this.DataContext = this.viewModel;

            this.SetCommandBindings();
        }

        private void UpdateCommandSink() {
            foreach(CommandSinkBinding cb in this.CommandBindings) {
                if(cb != null) {
                    cb.CommandSink = this.viewModel;
                }
            }
        }

        private void SetCommandBindings() {
            this.CommandBindings.Clear();
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.RelayoutCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = ApplicationCommands.New, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = ApplicationCommands.Open, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ExitCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ExpandNodeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ExpandNodeWithSubtreeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.CollapseNodeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.EditXmlNodeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.EditXmlElementCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SaveXmlElementCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SaveVisualizationCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = ApplicationCommands.Undo, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = ApplicationCommands.Redo, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SaveDocumentCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SaveDocumentAsCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.EditTextNodeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SettingsCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.AddChildElementCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.AddChildTextNodeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.CreateRootElementCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.RemoveNodeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ExpandAllCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = ApplicationCommands.Close, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ValidateXsdCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.CancelValidateXsdCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ValidateDtdCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.CancelValidateDtdCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SelectNodesCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ExportCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = ApplicationCommands.Print, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ChangeColorCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.ChangeAppearanceCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.MoveNodeLeftCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.MoveNodeRightCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SelectSubtreeForCopyingCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.SelectSubtreeForMovingCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.PasteSubtreeCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.AttachDtdCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XmlViewModel.RemoveDtdCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XsdViewModel.CreateXsdElementCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XsdViewModel.EditXsdElementCommand, CommandSink = this.viewModel });
            this.CommandBindings.Add(new CommandSinkBinding { Command = XsdViewModel.CheckXsdCommand, CommandSink = this.viewModel });
        }

        //public void Open(string path) {
        //    this.xmlModel = ModelFactory.CreateXmlModel(path);
        //    this.controller = ControllerFactory.CreateController(this.xmlModel, this);
        //    this.viewModel = new XmlViewModel(this.application, this.controller, this.xmlModel, this);
        //    this.viewModel.PropertyChanged += viewModel_PropertyChanged;

        //    DataContext = this.viewModel;            
        //}

        /// <summary>
        /// Sets the progress bar visibility.
        /// </summary>
        /// <param name="visibility">if set to <c>true</c> [visibility].</param>
        public void SetProgressBarVisibility(bool visibility) {
            if(visibility) {
                this.ProgressBar.Visibility = Visibility.Visible;
            } else {
                this.ProgressBar.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Sets the progress bar value.
        /// </summary>
        /// <param name="value">The value.</param>
        public void SetProgressBarValue(int value) {
            this.ProgressBar.Value = value;
        }

        /// <summary>
        /// Changes the mode.
        /// </summary>
        /// <param name="mode">The mode.</param>
        public void ChangeMode(ApplicationMode mode) {
            switch(mode) {
                case ApplicationMode.Xml:
                    this.xmlModel = ModelFactory.CreateXmlModel();
                    this.controller.SetModel(xmlModel);
                    break;
                case ApplicationMode.Xsd:
                    this.xsdModel = ModelFactory.CreateXsdModel();
                    this.controller.SetModel(xsdModel);
                    break;
            }
        }

        /// <summary>
        /// Gets the current mouse position.
        /// </summary>
        /// <returns></returns>
        public Point GetCurrentMousePosition() {
            Point pos = Mouse.GetPosition(this);
            Point pos2 = PointToScreen(pos);

            return pos2;
        }

        /// <summary>
        /// Prints the canvas.
        /// </summary>
        public void PrintCanvas() {
            PrintDialog pd = new PrintDialog();
            var toPrint = ExportArea;
            var transform = toPrint.LayoutTransform;

            try {
                if(pd.ShowDialog() != true) {
                    return;
                }

                //get selected printer capabilities
                System.Printing.PrintCapabilities capabilities = pd.PrintQueue.GetPrintCapabilities(pd.PrintTicket);

                //get scale of the print wrt to screen of WPF visual
                double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / toPrint.ActualWidth, capabilities.PageImageableArea.ExtentHeight / toPrint.ActualHeight);

                //Transform the Visual to scale
                toPrint.LayoutTransform = new ScaleTransform(scale, scale);

                //get the size of the printer page
                Size sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);

                //update the layout of the visual to the printer page size.
                toPrint.Measure(sz);

                toPrint.Arrange(new Rect(new Point(capabilities.PageImageableArea.OriginWidth, capabilities.PageImageableArea.OriginHeight), sz));
                toPrint.UpdateLayout();
                //now print the visual to printer to fit on the one page.

                pd.PrintVisual(toPrint, this.Title);
            } catch(Exception ex) {
                bool rethrow = ExceptionPolicy.HandleException(ex, defaultExceptionPolicy);
                if(rethrow) {
                    throw;
                }
            } finally {
                toPrint.LayoutTransform = transform;
            }
        }

        /// <summary>
        /// Exports the canvas.
        /// </summary>
        public void ExportCanvas() {
            var item = ExportArea;
            var oldBackground = item.Background;
            var transform = item.LayoutTransform;

            try {
                Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
                dlg.Title = "Export";
                dlg.Filter = "JPEG (*.jpg)|*.jpg|BMP (*.bmp)|*.bmp|PNG (*.png)|*.png|TIFF (*.tiff)|*.tiff|GIF (*.gif)|*.gif|XPS *.xps|*.xps";
                dlg.FilterIndex = 1;
                dlg.RestoreDirectory = true;

                var res = dlg.ShowDialog();
                if(res.HasValue && res.Value) {
                    string path = dlg.FileName;
                    string extension = System.IO.Path.GetExtension(path).ToLower();

                    BitmapEncoder encoder = null;
                    if(extension == ".jpg") {
                        encoder = new JpegBitmapEncoder();
                    } else if(extension == ".bmp") {
                        encoder = new BmpBitmapEncoder();
                    } else if(extension == ".png") {
                        encoder = new PngBitmapEncoder();
                    } else if(extension == ".tiff") {
                        encoder = new TiffBitmapEncoder();
                    } else if(extension == ".gif") {
                        encoder = new GifBitmapEncoder();
                    } else if(extension == ".xps") {
                        encoder = null;
                    } else {
                        return;
                    }

                    item.LayoutTransform = null;
                    item.Background = new SolidColorBrush(Colors.White);
                    Size size = new Size(item.ActualWidth, item.ActualHeight);

                    if(encoder != null) {
                        item.UpdateLayout();

                        RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96d, 96d, PixelFormats.Default);
                        renderTargetBitmap.Render(item);

                        encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

                        using(var fs = File.Create(path)) {
                            encoder.Save(fs);
                        }

                        item.UpdateLayout();
                    } else {
                        //designerCanvas.Measure(size);
                        //designerCanvas.Arrange(new Rect(size));
                        item.UpdateLayout();

                        // Open new package
                        Package package = Package.Open(path, FileMode.Create);
                        // Create new xps document based on the package opened
                        XpsDocument doc = new XpsDocument(package);
                        // Create an instance of XpsDocumentWriter for the document
                        XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
                        // Write the canvas (as Visual) to the document
                        writer.Write(item);
                        // Close document
                        doc.Close();
                        // Close package
                        package.Close();

                        item.UpdateLayout();
                    }
                }
            } catch(Exception ex) {
                bool rethrow = ExceptionPolicy.HandleException(ex, defaultExceptionPolicy);
                if(rethrow) {
                    throw;
                }
            } finally {
                item.LayoutTransform = transform;
                item.Background = oldBackground;
            }
        }
        #endregion

        #region Protected
        /// <summary>
        /// Handles the PropertyChanged event of the viewModel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        void viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
            // buttons don't get updated automatically so we need to help it...
            switch(e.PropertyName) {
                case "ValidatingXsd":
                case "ValidatingDtd":
                    CommandManager.InvalidateRequerySuggested();
                    break;
            }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Window.Closing"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.ComponentModel.CancelEventArgs"/> that contains the event data.</param>
        protected override void OnClosing(System.ComponentModel.CancelEventArgs e) {
            base.OnClosing(e);

            if(e.Cancel == false) {
                this.application.RemoveWindow(this);
            }
        }
        #endregion
        #endregion

        #region Event handlers
        /// <summary>
        /// Runs when ExitCommand is executed.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        protected void ExitCommandExecute(object sender, ExecutedRoutedEventArgs e) {
            System.Windows.Application.Current.Shutdown();
        }

        private void zoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            if(e.OldValue != 0) {
                svCanvas.ScrollToHorizontalOffset((svCanvas.HorizontalOffset / e.OldValue) * e.NewValue);
                svCanvas.ScrollToVerticalOffset((svCanvas.VerticalOffset / e.OldValue) * e.NewValue);
            } else {
                svCanvas.ScrollToHorizontalOffset(svCanvas.HorizontalOffset * e.NewValue);
                svCanvas.ScrollToVerticalOffset(svCanvas.VerticalOffset * e.NewValue);
            }
        }

        private void OnStatusTextBoxTextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) {
            this.StatusTextBox.ScrollToEnd();
        }

        /// <summary>
        /// Adds the message to status.
        /// </summary>
        /// <param name="message">The message.</param>
        public void AddMessageToStatus(string message) {
            this.Dispatcher.BeginInvoke(new Action(() => {
                lock(this.StatusTextBox) {
                    this.StatusTextBox.AppendText(string.Format("{0}\n", message));
                    if(this.StatusTextBox.LineCount > 500) {
                        this.StatusTextBox.Clear();
                    }
                }
            }));
        }
        #endregion
    }
}
